package main.model.persistence;

import java.awt.Point;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import controller.editor.EditorController;

import main.model.Attribute;
import main.model.Cardinality;
import main.model.Component;
import main.model.Diagram;
import main.model.Entity;
import main.model.Hierarchy;
import main.model.LayoutComponents;
import main.model.Project;
import main.model.Relation;


public class DiagramXmlReader {

	private static String MASTER = "maestra";
	private static String COMPOSITION = "composicion";
	private static String ID = "id";
	static private String DIAGRAM = "diagrama";
	static private String ENTITIES = "entidades";
	static private String RELATIONS = "relaciones";
	static private String ENTITY = "entidad";
	static private String RELATION = "relacion";
	static private String HIERARCHY = "jerarquia";
	static private String HIERARCHIES = "jerarquias";
	static private String NAME = "nombre";
	static private String TYPE = "tipo";
	static private String ATTRIBUTE = "atributo";
	static private String ATTRIBUTES = "atributos";
	static private String CARDINALITIES = "cardinalidades";
	static private String CARDINALITY = "cardinalidad";
	static private String MAX = "maxima";
	static private String MIN = "minima";
	static private String RELATED_ENTITIES = "entidadesRelacionadas";
	static private String PARENT_DIAGRAM = "diagramaPadre";
	static private String PARENT_ENTITY = "entidadPadre";
	static private String CHILD_ENTITIES = "entidadesHijas";
	static private String POSITION = "posicion";
	static private String X = "x";
	static private String Y = "y";

	private boolean isDiagram = false;
	private boolean isEntities = false; 
	private boolean isEntity = false;
	private boolean isAttributes = false;
	private boolean isAttribute = false;
	private boolean isCardinalities = false;
	private boolean isCardinality = false;
	private boolean existsComposerAttribute = false;
	private boolean isRelations = false;
	private boolean isRelation = false;
	private boolean isRelatedEntities = false;
	private boolean isHierarchies = false;
	private boolean isHierarchy = false;
	private boolean isParentEntity = false;
	private boolean isChildEntities = false;

	private List<Entity> entities;
	private List<Relation> relations;
	private Diagram diagram = null;
	private Diagram currentDiagram = null;
	private Entity entity = null;
	private Attribute attribute = null;
	private List<Attribute> attributes;
	private Relation relation = null;
	private Hierarchy hierarchy = null;
	private ArrayList<Hierarchy> hierarchies = null;
	private Cardinality cardinality = null;
	private Attribute composerAttribute = null;
	private ArrayList<Entity> childEntities = null;

	XMLInputFactory factory = XMLInputFactory.newInstance();
	private String nameDiagram = null;
	private String cardinalityName;
	private String parentEntityName;
	private boolean isAtrributeId = false;
	private String parentDiagram = null;


	private LayoutComponents layoutComponents;
	private Map<Component, Point> components = null;
	private Component component = null;
	private Point point = null;
	private EditorController controller;

	public DiagramXmlReader(EditorController controller) {
		this.setController(controller);
	}

	public void readFiles(String pathDiagrams,String pathPositions) throws XMLStreamException, IOException{
		this.readTo(pathDiagrams);
		this.readPositions(pathPositions);
	}

	public void readPositions(String path) throws XMLStreamException, IOException{
		FileReader file = new FileReader(path);

		XMLStreamReader reader = factory.createXMLStreamReader(file);
		
		while (reader.getEventType() != XMLStreamConstants.END_DOCUMENT) {
			Integer next = reader.next();
			switch (next) {
			
			case XMLStreamConstants.START_ELEMENT:
				if (reader.getLocalName().equals(DIAGRAM)) {
					this.isDiagram = true;
					this.layoutComponents = new LayoutComponents();
					this.components = new HashMap<Component, Point>();
				}
				if (reader.getLocalName().equals(ENTITIES)) {
					this.isEntities = true;
				}
				if (reader.getLocalName().equals(ENTITY)) {
					this.isEntity = true;
				}
				if (reader.getLocalName().equals(ATTRIBUTES)) {
					this.isAttributes = true;
				}
				if (reader.getLocalName().equals(ATTRIBUTE)) {
					this.isAttribute = true;
				}
				if (reader.getLocalName().equals(RELATIONS)) {
					this.isRelations = true;
				}
				if (reader.getLocalName().equals(RELATION)) {
					this.isRelation = true;
				}
				if (reader.getLocalName().equals(NAME)) {
					String name = reader.getElementText();
					if ( this.isAttribute || this.isEntity || this.isRelation ){
						this.component = this.currentDiagram.getComponentByName(name);
					} else if (this.isDiagram){
						this.currentDiagram = this.controller.getProject().getDiagramByName(name);
					}
				}
				if (reader.getLocalName().equals(POSITION)) {
					this.point  = new Point();
				}
				if (reader.getLocalName().equals(X)) {
					this.point.x = Integer.parseInt(reader.getElementText());
				}
				if (reader.getLocalName().equals(Y)) {
					this.point.y = Integer.parseInt(reader.getElementText());
				}
				break;	
			case XMLStreamConstants.END_ELEMENT:	
				if (reader.getLocalName().equals(DIAGRAM)) {
					this.isDiagram = false;
					this.layoutComponents.setLayout(this.components);
					this.components = null;
					this.controller.getProject().addDiagram(this.currentDiagram, this.layoutComponents);
					this.currentDiagram = null;
				}
				if (reader.getLocalName().equals(ENTITIES)) {
					this.isEntities = false;
				}
				if (reader.getLocalName().equals(ENTITY)) {
					this.isEntity = false;
					this.components.put(this.component, this.point);
					this.component = null;
					this.point = null;
				}
				if (reader.getLocalName().equals(ATTRIBUTES)) {
					this.isAttributes = false;
				}
				if (reader.getLocalName().equals(ATTRIBUTE)) {
					this.isAttribute = false;
					this.components.put(this.component, this.point);
					this.component = null;
					this.point = null;
				}
				if (reader.getLocalName().equals(RELATIONS)) {
					this.isRelations = false;
				}
				if (reader.getLocalName().equals(RELATION)) {
					this.isRelation = false;
					this.components.put(this.component, this.point);
					this.component = null;
					this.point = null;
				}

				break;
			}
		}
		reader.close(); // Recycles the reader.
		file.close(); // Underlying stream has to be closed explicitly.
	}

	public void readTo(String path) throws XMLStreamException, IOException{

		FileReader file = new FileReader(path);

		XMLStreamReader reader = factory.createXMLStreamReader(file);

		while (reader.getEventType() != XMLStreamConstants.END_DOCUMENT) {
			switch (reader.next()) {

			case XMLStreamConstants.START_ELEMENT:
				if (reader.getLocalName().equals(DIAGRAM)) {
					this.isDiagram = true;
				}
				if (reader.getLocalName().equals(ENTITIES)) {
					this.entities = new ArrayList<Entity>();
					this.isEntities = true;
				}
				if (reader.getLocalName().equals(ENTITY)) {
					this.isEntity = true;
					this.entity = new Entity();
					this.entity.setIsMasterEntity(Boolean.parseBoolean(reader.getAttributeValue("",MASTER)));
					this.parentDiagram  = reader.getAttributeValue("",PARENT_DIAGRAM);

				}
				if (reader.getLocalName().equals(HIERARCHIES)) {
					this.isHierarchies = true;
					this.hierarchies = new ArrayList<Hierarchy>();
				}
				if (reader.getLocalName().equals(HIERARCHY)) {
					this.isHierarchy = true;
					this.hierarchy = new Hierarchy();
				}
				if (reader.getLocalName().equals(PARENT_ENTITY)) {
					this.isParentEntity = true;
					String parentEntityName = reader.getElementText();
					for ( Entity e : this.entities ){
						if ( e.getName().equals(parentEntityName) ){
							this.hierarchy.setParent(e);
						}
					}
				}

				if (reader.getLocalName().equals(TYPE)) {
					String type = reader.getElementText(); 
					if ( this.isHierarchy ){
						if ( this.hierarchy.getType() == null ){
							this.hierarchy.setType(type);
						}
					}
				}
				if (reader.getLocalName().equals(NAME)) {
					String name = reader.getElementText();
					if ( this.isDiagram ){
						if ( this.nameDiagram == null){
							this.nameDiagram = name;
							this.diagram = new Diagram(this.nameDiagram);
							this.controller.getProject().addDiagramToCollection(this.diagram);
						}
					}
					if ( this.isEntity ){
						if ( this.entity.getName() == null){
							this.entity.setName(name);
						}
					}
					if ( this.isAttribute ){
						if ( this.attribute.getName() == null ){
							this.attribute.setName(name);
						}	
					}
					if ( this.isRelation ){
						if ( this.relation.getName() == null ){
							this.relation.setName(name);
						}	
					}
					if ( this.isHierarchy ){
						if ( this.hierarchy.getName() == null ){
							this.hierarchy.setName(name);
						}
					}
				}
				if (reader.getLocalName().equals(ATTRIBUTES)) {
					this.isAttributes = true;
					if ( this.isAttribute ){
						// es un atributo compuesto -- solo un nivel contemplatado
						this.composerAttribute = this.attribute;
					} else {
						this.attributes = new ArrayList<Attribute>();
						this.isAtrributeId = Boolean.parseBoolean(reader.getAttributeValue("",ID));
					}

				}
				if (reader.getLocalName().equals(ATTRIBUTE)) {
					if ( this.isCardinality ){
						this.cardinalityName = reader.getElementText();
					} else {
						this.isAttribute = true;
						this.attribute = new Attribute();
						this.attribute.setType(reader.getAttributeValue("",TYPE));
					}
				}
				if (reader.getLocalName().equals(CARDINALITIES)) {
					this.isCardinalities = true;
				}
				if (reader.getLocalName().equals(CARDINALITY)) {
					this.isCardinality = true;
					this.cardinality = new Cardinality();
				}
				if (reader.getLocalName().equals(MAX)) {
					this.cardinality.setMax(reader.getElementText());
				}
				if (reader.getLocalName().equals(MIN)) {
					this.cardinality.setMin(reader.getElementText());
				}
				if (reader.getLocalName().equals(RELATIONS)) {
					this.isRelations = true;
					this.relations = new ArrayList<Relation>();
				}
				if (reader.getLocalName().equals(RELATION)) {
					this.isRelation = true;
					this.relation = new Relation();
					this.relation.setIsComposition(Boolean.parseBoolean(reader.getAttributeValue("",COMPOSITION)));

				}

				if (reader.getLocalName().equals(RELATED_ENTITIES)){
					this.isRelatedEntities  = true;
				}
				break;

			case XMLStreamConstants.END_ELEMENT:
				if (reader.getLocalName().equals(DIAGRAM)) {
					this.diagram.setEntities(this.entities);
					this.diagram.setRelations(this.relations);
					this.diagram.setHierarchies(this.hierarchies);
					this.getController().addDiagramToProject(this.diagram);
					this.entities = null;
					this.nameDiagram = null;
					this.relations = null;
					this.hierarchies = null;
					this.diagram = null;
					this.layoutComponents = null;
					this.isDiagram = false;
				}
				if (reader.getLocalName().equals(ENTITIES)) {
					this.isEntities = false;
				}
				if (reader.getLocalName().equals(ENTITY)) {
					this.isEntity = false;
					if ( this.isRelation ){
						for ( Entity e : this.entities ){
							if ( e.getName().equals(this.entity.getName()) ){
								this.relation.addEntity(e);
							}
						}

					} else {
						if ( this.isHierarchy ){
							for ( Entity e : this.entities ){
								if ( e.getName().equals(this.entity.getName()) ){
									this.hierarchy.addEntity(e);
								}
							}
						} else {
							if (this.diagram.getName().equals(this.parentDiagram)){
								this.entity.setDiagramParent((this.parentDiagram));
							} else {
								this.entity = this.controller.getProject().getDiagramByName(this.parentDiagram).getEntityByName(this.entity.getName());
							}
							this.entities.add(this.entity);
						}	
					}
					this.entity = null;
				}
				if (reader.getLocalName().equals(HIERARCHIES)) {
					this.isHierarchies = false;
				}
				if (reader.getLocalName().equals(HIERARCHY)) {
					this.isHierarchy = false;
					this.hierarchies.add(this.hierarchy);
					this.hierarchy = null;
				}

				if (reader.getLocalName().equals(RELATIONS)) {
					this.isRelations = false;
				}
				if (reader.getLocalName().equals(RELATION)) {
					this.isRelation = false;
					this.relations.add(this.relation);
					this.relation = null;
				}
				if (reader.getLocalName().equals(RELATED_ENTITIES)){
					this.isRelatedEntities  = false;
				}
				/**
				 * TODO esto esta medio negro, se podria arreglar pensandolo mejor. 
				 *  Esta solo aguantado para 1 nivel de atributos
				 *  composerAttribute : tiene los atributos hijos
				 *  existsComposerAttribute : booleano que me dice si tengo un atributo compuesto.
				 */

				if (reader.getLocalName().equals(ATTRIBUTES)) {
					this.isAttributes = false;
					if ( this.composerAttribute != null ){
						this.attributes.add(this.composerAttribute);
						this.composerAttribute = null;
						this.existsComposerAttribute  = true;
					} else {
						if ( isRelation ){
							if ( this.isAtrributeId ){
								relation.setIdAttributes(this.attributes);
								this.isAtrributeId = false;
							} else {
								relation.setAttributes(this.attributes);
							}
						}else if ( isEntity ){
							if ( this.isAtrributeId ){
								entity.setIdAttributes(this.attributes);
								this.isAtrributeId = false;
							} else {
								entity.setAttributes(this.attributes);
							}
						}
						this.attributes = null;
					}
				}
				if (reader.getLocalName().equals(ATTRIBUTE)) {
					this.isAttribute = false;
					if ( this.composerAttribute != null ){
						this.composerAttribute.addAttribute(this.attribute);
					} else {
						if ( this.existsComposerAttribute ){
							this.existsComposerAttribute = false;
						} else {
							this.attributes.add(this.attribute);
						}
					}
				}
				if (reader.getLocalName().equals(CARDINALITIES)) {
					this.isCardinalities = false;
				}
				if (reader.getLocalName().equals(CARDINALITY)) {
					this.isCardinality = false;
					if ( this.isRelation && this.isRelatedEntities ){
						for ( Entity e : this.entities ){
							if ( e.getName().equals(this.entity.getName()) ){
								this.relation.setCardinalityToEntity(e, this.cardinality);
							}
						}
					}
					if ( this.isRelation && !this.isRelatedEntities ){
						if ( this.existsComposerAttribute ){
							for (int i = 0; i < this.attributes.size(); i++) {
								for (Attribute attr : this.attributes.get(i).getAttributes()) {
									if ( attr.getName().equals(this.cardinalityName) ){
										attr.setCardinality(this.cardinality);
									}
								}
							}
						} else {
							Boolean attFound = Boolean.FALSE;
							for (Attribute attr : this.relation.getAttributes()) {
								if ( attr.getName().equals(this.cardinalityName) ){
									attr.setCardinality(this.cardinality);
									attFound = Boolean.TRUE;
								}
							}
							if (!attFound){
								for (Attribute attr : this.relation.getIdAttributes()) {
									if ( attr.getName().equals(this.cardinalityName) ){
										attr.setCardinality(this.cardinality);
										attFound = Boolean.TRUE;
									}
								}	
							}
						}
					}

					if ( this.isEntity ){
						if ( this.existsComposerAttribute ){
							for (int i = 0; i < this.attributes.size(); i++) {
								for (Attribute attr : this.attributes.get(i).getAttributes()) {
									if ( attr.getName().equals(this.cardinalityName) ){
										attr.setCardinality(this.cardinality);
									}
								}
							}
						} else {
							Boolean attFound = Boolean.FALSE;
							for (Attribute attr : this.entity.getAttributes()) {
								if ( attr.getName().equals(this.cardinalityName) ){
									attr.setCardinality(this.cardinality);
									attFound = Boolean.TRUE;
								}
							}
							if (!attFound){
								for (Attribute attr : this.entity.getIdAttributes()) {
									if ( attr.getName().equals(this.cardinalityName) ){
										attr.setCardinality(this.cardinality);
										attFound = Boolean.TRUE;
									}
								}	
							}
						}
					}

					this.cardinality = null;
				}
				break;
			} 
		}

		reader.close(); // Recycles the reader.
		file.close(); // Underlying stream has to be closed explicitly.

	}

	public void loadProject(String principal, Set<String> fileNames) {
		try {
			this.readFiles(principal + "-comp.xml", principal + "-rep.xml");
			for (String names : fileNames) {
				this.readFiles(names + "-comp.xml", names + "-rep.xml");
			}
		} catch (XMLStreamException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public EditorController getController() {
		return controller;
	}

	public void setController(EditorController controller) {
		this.controller = controller;
	}
}
